<!--
 The BSD 3-Clause License

 Copyright 2022 - DATATRONiQ GmbH (https://datatroniq.com)
 Copyright (c) 2018-2022 Klaus Landsdorf (http://node-red.plus/)
 Copyright 2015,2016 - Mika Karaila, Valmet Automation Inc. (node-red-contrib-opcua)
 All rights reserved.
 node-red-contrib-iiot-opcua
-->

<script type="text/javascript">
  RED.nodes.registerType('OPCUA-IIoT-Flex-Server', {
    category: 'IIoT',
    color: '#ABCDEF',
    defaults: {
      port: {value: '55380', required: true},
      endpoint: {value: ''},
      acceptExternalCommands: {value: true},
      maxAllowedSessionNumber: {value: ''},
      maxConnectionsPerEndpoint: {value: ''},
      maxAllowedSubscriptionNumber: {value: ''},
      alternateHostname: {value: ''},
      name: {value: ''},
      showStatusActivities: {value: false},
      showErrors: {value: false},
      allowAnonymous: {value: true},
      individualCerts: {value: false},
      isAuditing: {value: false},
      serverDiscovery: {value: true},
      users: { value: [] },
      xmlsets: { value: [] },
      publicCertificateFile: {value: ''},
      privateCertificateFile: {value: ''},
      registerServerMethod: {value: 1},
      discoveryServerEndpointUrl: {value: ''},
      capabilitiesForMDNS: {value: ''},
      maxNodesPerRead: {value: 1000, validate:function(v) { return ((v === '') || (RED.validators.number(v) && (v > 0) && (v <= 100000))) }},
      maxNodesPerBrowse: {value: 2000, validate:function(v) { return ((v === '') || (RED.validators.number(v) && (v > 0) && (v <= 200000))) }},
      delayToClose: {value: 1000, validate:function(v) { return ((v === '') || (RED.validators.number(v) && (v >= 100) && (v <= 15000))) }},
      addressSpaceScript: { value: "function constructAlarmAddressSpace(server, addressSpace, eventObjects, done) {\n    // server = the created node-opcua server\n    // addressSpace = script placeholder\n    // eventObjects = to hold event variables in memory from this script\n    \n    // internal global sandbox objects are \n    // node = node of the flex server, \n    // coreServer = core iiot server object for debug and access to nodeOPCUA,\n    // and scriptObjects to hold variables and functions\n    const LocalizedText = coreServer.core.nodeOPCUA.LocalizedText\n    const namespace = addressSpace.getOwnNamespace()\n\n    coreServer.internalDebugLog('init dynamic address space')\n    node.warn('construct new address space for OPC UA')\n    \n    // from here - see the node-opcua docs how to build address sapces\n    let tanks = namespace.addObject({\n        browseName: 'Tanks',\n        description: 'The Object representing some tanks',\n        organizedBy: addressSpace.rootFolder.objects,\n        notifierOf: addressSpace.rootFolder.objects.server\n    })\n    \n    let oilTankLevel = namespace.addVariable({\n        browseName: 'OilTankLevel',\n        displayName: [\n          new LocalizedText({text: 'Oil Tank Level', locale: 'en-US'}),\n          new LocalizedText({text: 'Öl Tank Füllstand', locale: 'de-DE'})\n        ],\n        description: 'Fill level in percentage (0% to 100%) of the oil tank',\n        propertyOf: tanks,\n        dataType: 'Double',\n        eventSourceOf: tanks\n    })\n    \n    // ---------------------------------------------------------------------------------\n    // Let's create a exclusive Limit Alarm that automatically raise itself\n    // when the tank level is out of limit\n    // ---------------------------------------------------------------------------------\n    let exclusiveLimitAlarmType = addressSpace.findEventType('ExclusiveLimitAlarmType')\n    if (nonExclusiveLimitAlarmType === null) throw new Error(\"nonExclusiveLimitAlarmType Error Message\")(exclusiveLimitAlarmType !== null)\n    \n    let oilTankLevelCondition = namespace.instantiateExclusiveLimitAlarm(exclusiveLimitAlarmType, {\n        componentOf: tanks,\n        conditionSource: oilTankLevel,\n        browseName: 'OilTankLevelCondition',\n        displayName: [\n          new LocalizedText({text: 'Oil Tank Level Condition', locale: 'en-US'}),\n          new LocalizedText({text: 'Öl Tank Füllstand Bedingung', locale: 'de-DE'})\n        ],\n        description: 'ExclusiveLimitAlarmType Condition',\n        conditionName: 'OilLevelCondition',\n        optionals: [\n          'ConfirmedState', 'Confirm' // confirm state and confirm Method\n        ],\n        inputNode: oilTankLevel,   // the letiable that will be monitored for change\n        highHighLimit: 0.9,\n        highLimit: 0.8,\n        lowLimit: 0.2\n    })\n    \n    // --------------------------------------------------------------\n    // Let's create a second letiable with no Exclusive alarm\n    // --------------------------------------------------------------\n    let gasTankLevel = namespace.addVariable({\n        browseName: 'GasTankLevel',\n        displayName: [\n          new LocalizedText({text: 'Gas Tank Level', locale: 'en-US'}),\n          new LocalizedText({text: 'Gas Tank Füllstand', locale: 'de-DE'})\n        ],\n        description: 'Fill level in percentage (0% to 100%) of the gas tank',\n        propertyOf: tanks,\n        dataType: 'Double',\n        eventSourceOf: tanks\n    })\n    \n    let nonExclusiveLimitAlarmType = addressSpace.findEventType('NonExclusiveLimitAlarmType')\n    if (nonExclusiveLimitAlarmType === null) throw new Error(\"nonExclusiveLimitAlarmType Error Message\")(nonExclusiveLimitAlarmType !== null)\n    \n    let gasTankLevelCondition = namespace.instantiateNonExclusiveLimitAlarm(nonExclusiveLimitAlarmType, {\n        componentOf: tanks,\n        conditionSource: gasTankLevel,\n        browseName: 'GasTankLevelCondition',\n        displayName: [\n          new LocalizedText({text: 'Gas Tank Level Condition', locale: 'en-US'}),\n          new LocalizedText({text: 'Gas Tank Füllstand Bedingung', locale: 'de-DE'})\n        ],\n        description: 'NonExclusiveLimitAlarmType Condition',\n        conditionName: 'GasLevelCondition',\n        optionals: [\n          'ConfirmedState', 'Confirm' // confirm state and confirm Method\n        ],\n        inputNode: gasTankLevel,   // the letiable that will be monitored for change\n        highHighLimit: 0.9,\n        highLimit: 0.8,\n        lowLimit: 0.2\n    })\n    \n    // variable with value\n    if(scriptObjects.testReadWrite === undefined || scriptObjects.testReadWrite === null) {\n            scriptObjects.testReadWrite = 1000.0\n    }\n    \n    let myVariables = namespace.addObject({\n        browseName: 'MyVariables',\n        description: 'The Object representing some variables',\n        organizedBy: addressSpace.rootFolder.objects,\n        notifierOf: addressSpace.rootFolder.objects.server\n    })\n    \n    if(coreServer.core) {\n        namespace.addVariable({\n            componentOf: myVariables,\n            nodeId: 'ns=1;s=TestReadWrite',\n            browseName: 'TestReadWrite',\n            displayName: [\n                new LocalizedText({text: 'Test Read and Write', locale: 'en-US'}),\n                new LocalizedText({text: 'Test Lesen Schreiben', locale: 'de-DE'})\n            ],\n            dataType: 'Double',\n            value: {\n                get: function () {\n                    return new coreServer.core.nodeOPCUA.Variant({\n                        dataType: 'Double',\n                        value: scriptObjects.testReadWrite\n                    })\n                },\n                set: function (variant) {\n                    scriptObjects.testReadWrite = parseFloat(variant.value)\n                    return coreServer.core.nodeOPCUA.StatusCodes.Good\n                }\n            }\n            \n        })\n        \n        let memoryVariable = namespace.addVariable({\n            componentOf: myVariables,\n            nodeId: 'ns=1;s=free_memory',\n            browseName: 'FreeMemory',\n            displayName: [\n                new LocalizedText({text: 'Free Memory', locale: 'en-US'}),\n                new LocalizedText({text: 'ungenutzer RAM', locale: 'de-DE'})\n            ],\n            dataType: 'Double',\n            \n            value: {\n              get: function () {\n                return new coreServer.core.nodeOPCUA.Variant({\n                  dataType: 'Double',\n                  value: coreServer.core.availableMemory()\n                })\n              }\n            }\n        })\n        addressSpace.installHistoricalDataNode(memoryVariable)\n       \n    } else {\n        coreServer.internalDebugLog('coreServer.core needed for coreServer.core.nodeOPCUA')\n    }\n\n    // hold event objects in memory \n    eventObjects.oilTankLevel = oilTankLevel\n    eventObjects.oilTankLevelCondition = oilTankLevelCondition\n    \n    eventObjects.gasTankLevel = gasTankLevel\n    eventObjects.gasTankLevelCondition = gasTankLevelCondition\n    \n    done()\n}"
      }
    },
    inputs: 1,
    outputs: 1,
    align: 'right',
    icon: 'icon.png',
    label: function () {
      return this.name || 'Flex-Server'
    },
    labelStyle: function () {
      return this.name ? 'node_label_italic' : ''
    },
    oneditprepare: function () {
      let node = this
      node.xmlsetSelect = []
      let cacheItemCount = 0
      let cacheItemCountSet = 0

      let tabs = RED.tabs.create({
        id: "node-input-server-tabs",
        onchange: function (tab) {
          $("#node-input-server-tabs-content").children().hide()
          $("#" + tab.id).show()
        }
      })

      tabs.addTab({
        id: "opcuaiiot-server-tab-settings",
        label: this._("opcua-iiot-contrib.tabs-label.settings")
      })

      tabs.addTab({
        id: "opcuaiiot-server-tab-limits",
        label: this._("opcua-iiot-contrib.tabs-label.limits")
      })

      tabs.addTab({
        id: "opcuaiiot-server-tab-security",
        label: this._("opcua-iiot-contrib.tabs-label.security")
      })

      tabs.addTab({
        id: "opcuaiiot-server-tab-users",
        label: this._("opcua-iiot-contrib.tabs-label.users")
      })

      tabs.addTab({
        id: "opcuaiiot-server-tab-ass",
        label: this._("opcua-iiot-contrib.tabs-label.ass")
      })

      tabs.addTab({
        id: "opcuaiiot-server-tab-discovery",
        label: this._("opcua-iiot-contrib.tabs-label.discovery")
      })

      // User Management
      if(node.users && node.users.length > 0) {
          cacheItemCount = node.users.length
          node.users.forEach(function(element, index, array) {
            generateUserEntry(element, index);
          })
      }

      // XML-Set Management
      if(node.xmlsets && node.xmlsets.length > 0) {
          cacheItemCountSet = node.xmlsets.length
          node.xmlsets.forEach(function(element, index, array) {
            generateXMLSetEntry(element, index);
          })
      }

      function generateUserEntry(user, id) {
        let container = $('<li/>', {style:
          "background: #fefefe; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"})
        let row = $('<div id="row' + id + '"/>').appendTo(container)

        $('<i style="color: #eee; cursor: move;" class="node-input-server-users-handle fa fa-bars"></i>').appendTo(row)

        let userField = $('<input/>', {
          id: "node-input-server-users-name" + id,
          class: 'opcuaUserName',
          type: "text",
          style: "margin-left:5px;width:160px;",
          placeholder: 'name'
        }).appendTo(row)

        let passwordField = $('<input/>', {
          id: "node-input-server-users-password" + id,
          class: 'opcuaUserPassword',
          type: "password",
          style: "margin: 0 auto;width:55%;min-width:60px;margin-left:5px",
          placeholder: 'password'
        }).appendTo(row)

        userField.val(user.name)
        passwordField.val(user.password)

        let finalspan = $('<span/>', {style: "float: right;margin-right: 10px;"}).appendTo(row)

        let removeUserButton = $('<a/>', {
          href: "#",
          id: 'node-button-user-remove' + id,
          class: "editor-button editor-button-small",
          style: "margin-top: 7px; margin-left: 5px;"
        }).appendTo(finalspan)

        $('<i/>', {class: "fa fa-remove"}).appendTo(removeUserButton)

        removeUserButton.click(function () {
          container.css({"background": "#fee"})
          container.fadeOut(300, function () {
            $(this).remove()
          });
        });

        $("#node-input-server-users-container").append(container)
      }

      function generateXMLSetEntry(xmlset, id) {
        let container = $('<li/>', {style:
            "background: #fefefe; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"})
        let row = $('<div id="xmlsetRow' + id + '"/>').appendTo(container)

        $('<i style="color: #eee; cursor: move;" class="node-input-server-xmlsets-handle fa fa-bars"></i>').appendTo(row)

        let nameField = $('<input/>', {
          id: "node-input-server-xmlsets-name" + id,
          type: "text",
          class: 'xmlsetName',
          style: "margin-left:5px;width:75px;",
          placeholder: 'name'
        }).appendTo(row)

        let pathField = $('<input/>', {
          id: "node-input-server-xmlsets-path" + id,
          class: 'xmlsetPath',
          type: "text",
          style: "margin: 0 auto;width:68%;min-width:60px;margin-left:5px",
          placeholder: 'public/vendor/opc-foundation/xml/Opc.ISA95.NodeSet2.xml'
        }).appendTo(row)

        nameField.val(xmlset.name)
        pathField.val(xmlset.path)

        let finalspan = $('<span/>', {style: "float: right;margin-right: 10px;"}).appendTo(row)

        let lookupItemButton = $('<a/>', {
          href: '#',
          id: 'node-button-xmlsets-lookup' + id,
          class: 'editor-button editor-button-small',
          style: 'margin-top: 7px; margin-left: 5px;'
        }).appendTo(finalspan)

        lookupItemButton.click(function () {
          let xmlsetLookupButton = $('#node-button-xmlsets-lookup' + id)
          let xmlsetLookupField = $('#xmlsetRow' + id + ' #node-input-server-xmlsets-path' + id)

          xmlsetLookupButton.addClass('disabled')

          $.getJSON('opcuaIIoT/xmlsets/public', function (data) {
            xmlsetLookupButton.removeClass('disabled')
            node.xmlsetSelect = []

            $.each(data, function (i, filename) {
              node.xmlsetSelect.push(filename)
            })

            xmlsetLookupField.autocomplete({
              source: node.xmlsetSelect,
              minLength: 0,
              close: function (event, ui) {
                xmlsetLookupField.autocomplete('destroy')
              }
            }).autocomplete('search', '')
          })
        })

        $('<i/>', {class: 'fa fa-search'}).appendTo(lookupItemButton)

        let removeXMLSetButton = $('<a/>', {
          href: "#",
          id: 'node-button-xmlset-remove' + id,
          class: "editor-button editor-button-small",
          style: "margin-top:7px;margin-left:5px;"
        }).appendTo(finalspan)

        $('<i/>', {class: "fa fa-remove"}).appendTo(removeXMLSetButton)

        removeXMLSetButton.click(function () {
          container.css({"background": "#fee"})
          container.fadeOut(300, function () {
            $(this).remove()
          });
        });

        $("#node-input-server-xmlsets-container").append(container)
      }

      $("#node-input-server-users-container").sortable({
        axis: "y",
        handle: ".node-input-server-users-handle",
        cursor: "move"
      });

      $("#node-input-server-xmlsets-container").sortable({
        axis: "y",
        handle: ".node-input-server-xmlsets-handle",
        cursor: "move"
      });

      $("#node-input-server-users-container .node-input-server-users-handle").disableSelection()

      $("#node-input-server-xmlsets-container .node-input-server-xmlsets-handle").disableSelection()

      $("#node-input-server-users-add").click(function () {
        if(!cacheItemCount || cacheItemCount < 0) {
          cacheItemCount = 0
        }
        generateUserEntry({ name: '', password: '' }, cacheItemCount++) // length is every time one more than index
        $("#node-input-server-users-container-div").scrollTop($("#node-input-server-users-container-div").get(0).scrollHeight)
      })

      $("#node-input-server-xmlsets-add").click(function () {
        if(!cacheItemCountSet || cacheItemCountSet < 0) {
          cacheItemCountSet = 0
        }
        generateXMLSetEntry({ name: '', path: '' }, cacheItemCountSet++) // length is every time one more than index
        $("#node-input-server-xmlsets-container-div").scrollTop($("#node-input-server-xmlsets-container-div").get(0).scrollHeight)
      })

      function switchDialogResize() {
        switchUserDialogResize()
        switchXMLSetDialogSetResize()
      }

      // dialog User handling
      function switchUserDialogResize() {
        let rows = $("#dialog-form>div:not(.node-input-server-users-container-row)")
        let height = $("#dialog-form").height()

        rows.each(function (index, row) {
          height -= row.outerHeight(true)
        })

        let editorRow = $("#dialog-form>div.node-input-server-users-container-row")
        height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")))
        $("#node-input-server-users-container-div").css("height", height + "px")
      }

      // dialog XML-Set handling
      function switchXMLSetDialogSetResize() {
        let rows = $("#dialogSet-form>div:not(.node-input-server-xmlsets-container-row)")
        let height = $("#dialogSet-form").height()

        rows.each(function (index, row) {
          height -= row.outerHeight(true)
        })

        let editorRow = $("#dialogSet-form>div.node-input-server-xmlsets-container-row")
        height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")))
        $("#node-input-server-xmlsets-container-div").css("height", height + "px")
      }

      $("#dialog").on("dialogresize", switchDialogResize)

      $("#dialog").on("dialogopen", function (ev) {
        let size = $("#dialog").dialog('option', 'sizeCache-switch')
        if (size) {
          $("#dialog").dialog('option', 'width', size.width)
          $("#dialog").dialog('option', 'height', size.height)
          switchDialogResize()
        } else {
          setTimeout(switchDialogResize, 10)
        }
      })

      $("#dialog").on("dialogclose", function (ev, ui) {
        $("#dialog").off("dialogresize", switchDialogResize)
      })

      // address space script
      node.editorAddressSpaceScript = RED.editor.createEditor({
        id: 'node-input-func-editor-addressSpaceScript',
        mode: 'ace/mode/javascript',
        value: $("#node-input-addressSpaceScript").val(),
        globals: {
          msg:true,
          context:true,
          RED: true,
          util: true,
          flow: true,
          global: true,
          console: true,
          Buffer: true,
          setTimeout: true,
          clearTimeout: true,
          setInterval: true,
          clearInterval: true,
          draggable: true
        }
      });

      $( function() {
        $( "#node-input-func-editor-addressSpaceScript" ).resizable();
      } );

      let element = document.getElementById('node-input-func-editor-addressSpaceScript');
      element.addEventListener("mouseup", resizeScript, false);

      function resizeScript () {
        node.editorAddressSpaceScript.resize()
      }

      // Certificate Management
      try {
        let certsCheckbox = $("#node-input-individualCerts");
        let configCertFields = $("#node-config-certFiles");

        if (node.individualCerts) {
          certsCheckbox.prop('checked', true);
          configCertFields.show();
        } else {
          certsCheckbox.prop('checked', false);
          configCertFields.hide();
        }

        certsCheckbox.change(function () {
          if ($(this).is(":checked")) {
            configCertFields.show();
          } else {
            configCertFields.hide();
            $('#node-config-input-publicCertificateFile').val('');
            $('#node-config-input-privateKeyFile').val('');
          }
        });

      } catch (err) {
        this.error(err);
      }
    },
    oneditsave: function () {
      let node = this

      let cacheUsers = $("#node-input-server-users-container").children()
      node.users = []
      cacheUsers.each(function () {
        node.users.push({
          name: $(this).find('.opcuaUserName').val(),
          password: $(this).find('.opcuaUserPassword').val(),
        })
      })

      let cacheXMLSets = $('#node-input-server-xmlsets-container').children()
      node.xmlsets = []
      cacheXMLSets.each(function () {
        node.xmlsets.push({
          name: $(this).find('.xmlsetName').val(),
          path: $(this).find('.xmlsetPath').val(),
        })
      })

      $("#node-input-addressSpaceScript").val(this.editorAddressSpaceScript.getValue());
      this.editorAddressSpaceScript.destroy();
      delete this.editorAddressSpaceScript;

      // console.log('well done editsave ...')
    }
  })
</script>

<script type="text/x-red" data-template-name="OPCUA-IIoT-Flex-Server">
    <div class="form-row">
        <ul style="background: #fff; min-width: 600px; margin-bottom: 20px;" id="node-input-server-tabs"></ul>
    </div>
    <div id="node-input-server-tabs-content" style="min-height: 170px;">
        <div id="opcuaiiot-server-tab-settings" style="display:none">
            <div class="form-row">
                <label for="node-input-port"><i class="fa fa-fort-awesom"></i> <span data-i18n="opcua-iiot-contrib.label.port"></span></label>
                <!-- dynamic Ports from 49152 to 65535 (c000hex bis FFFFhex) -->
                <input type="text" id="node-input-port" placeholder="Ports 49152 bis 65535 (c000hex bis FFFFhex)">
            </div>
            <div class="form-row">
                <label for="node-input-endpoint"><i class="icon-tasks"></i> <span data-i18n="opcua-iiot-contrib.label.endpoint"></span></label>
                <input type="text" id="node-input-endpoint" placeholder="UA/NodeREDFlexIIoTServer">
            </div>
            <div class="form-row">
                <label for="node-input-alternateHostname"><i class="fa fa-server"></i> <span data-i18n="opcua-iiot-contrib.label.alternateHostname"></span></label>
                <input type="text" id="node-input-alternateHostname" placeholder="">
            </div>
            <hr>
            <div class="form-row">
                <label for="node-input-delayToClose"><i class="icon-time"></i> <span data-i18n="opcua-iiot-contrib.label.delayToClose"></span></label>
                <input type="text" id="node-input-delayToClose" placeholder="1000" style="width:80px">
            </div>
            <div class="form-row">
                <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
                <input type="text" id="node-input-name" placeholder="">
            </div>
            <div class="form-row">
                <label style="min-width:160px" for="node-input-isAuditing"><i class="fa fa-th"></i>
                <span data-i18n="opcua-iiot-contrib.label.isAuditing"></span></label>
                <input type="checkbox" id="node-input-isAuditing" style="max-width:30px">
            </div>
            <hr>
            <div class="form-row">
                <label style="min-width:160px" for="node-input-showStatusActivities"><i class="fa fa-bolt"></i>
                <span data-i18n="opcua-iiot-contrib.label.showActivities"></span></label>
                <input type="checkbox" id="node-input-showStatusActivities" style="max-width:30px">
            </div>
            <div class="form-row">
                <label style="min-width:160px" for="node-input-showErrors"><i class="fa fa-exclamation-circle"></i>
                <span data-i18n="opcua-iiot-contrib.label.showErrors"></span></label>
                <input type="checkbox" id="node-input-showErrors" style="max-width:30px">
            </div>
        </div>
        <div id="opcuaiiot-server-tab-limits" style="display:none">
            <div class="form-row">
                <label for="node-input-maxAllowedSessionNumber"><i class="icon-list"></i> <span data-i18n="opcua-iiot-contrib.label.maxAllowedSessionNumber"></span></label>
                <input type="text" id="node-input-maxAllowedSessionNumber" placeholder="10" style="width:80px">
            </div>
            <div class="form-row">
                <label for="node-input-maxConnectionsPerEndpoint"><i class="icon-list"></i> <span data-i18n="opcua-iiot-contrib.label.maxConnectionsPerEndpoint"></span></label>
                <input type="text" id="node-input-maxConnectionsPerEndpoint" placeholder="10" style="width:80px">
            </div>
            <div class="form-row">
                <label for="node-input-maxAllowedSubscriptionNumber"><i class="icon-list"></i> <span data-i18n="opcua-iiot-contrib.label.maxAllowedSubscriptionNumber"></span></label>
                <input type="text" id="node-input-maxAllowedSubscriptionNumber" placeholder="50" style="width:80px">
            </div>
            <div class="form-row">
                <label for="node-input-maxNodesPerRead"><i class="icon-list"></i> <span data-i18n="opcua-iiot-contrib.label.maxNodesPerRead"></span></label>
                <input type="text" id="node-input-maxNodesPerRead" placeholder="1000" style="width:80px">
            </div>
            <div class="form-row">
                <label for="node-input-maxNodesPerBrowse"><i class="icon-list"></i> <span data-i18n="opcua-iiot-contrib.label.maxNodesPerBrowse"></span></label>
                <input type="text" id="node-input-maxNodesPerBrowse" placeholder="2000" style="width:80px">
            </div>
        </div>
        <div id="opcuaiiot-server-tab-security" style="display:none">
            <div class="form-row">
                <!-- SecurityPolicy enum via REST -->
                <label for="node-input-securityPolicy" style="min-width:140px"><i class="fa fa-certificate"></i>
                <span data-i18n="opcua-iiot-contrib.label.securityPolicy"></span></label>
                <ul style="margin-left:80px">
                    <li>None</li>
                    <li>Basic128Rsa15</li>
                    <li>Basic256</li>
                    <li>Basic256Sha256</li>
                </ul>
            </div>
            <div class="form-row">
                <!-- MessageSecurityMode enum via REST -->
                <label for="node-input-securityMode" style="min-width:140px"><i class="fa fa-key"></i>
                <span data-i18n="opcua-iiot-contrib.label.securityMode"></span></label>
                <ul style="margin-left:80px">
                    <li>None</li>
                    <li>Sign</li>
                    <li>Sign&Encrypt</li>
                </ul>
            </div>
            <div class="form-row">
                <label style="min-width:140px">
                    <i class="fa fa-certificate"></i>
                    <span data-i18n="opcua-iiot-contrib.label.certificateFiles"></span>
                </label>
            </div>
            <div class="form-row">
                <label style="min-width:280px">
                    <i class="fa fa-file"></i>
                    <span data-i18n="opcua-iiot-contrib.label.individualCerts"></span>
                    <input type="checkbox" id="node-input-individualCerts" style="max-width:30px">
                </label>
            </div>
            <div id="node-config-certFiles">
                <hr>
                <div class="form-row">
                    <label for="node-input-publicCertificateFile"><i class="fa fa-file"></i>
                    <span data-i18n="opcua-iiot-contrib.label.publicCertificateFile"></span></label>
                    <input type="text" id="node-input-publicCertificateFile" placeholder="./certificates/server_selfsigned_cert_2048.pem" style="min-width:480px;width:100%">
                </div>
                <div class="form-row">
                    <label for="node-input-privateCertificateFile"><i class="fa fa-file"></i>
                    <span data-i18n="opcua-iiot-contrib.label.privateCertificateFile"></span></label>
                    <input type="text" id="node-input-privateCertificateFile" placeholder="./certificates/PKI/own/private/private_key.pem" style="min-width:480px;width:100%">
                </div>
                <hr>
            </div>
            <div class="form-row">
                 <label for="node-input-allowAnonymous" style="min-width:140px"><i class="icon-lock"></i>
                 <span data-i18n="opcua-iiot-contrib.label.allowAnonymous"></span></label>
                <input type="checkbox" id="node-input-allowAnonymous" style="max-width:35px">
            </div>
        </div>
        <div id="opcuaiiot-server-tab-users" style="display:none">
            <h4><span data-i18n="opcua-iiot-contrib.label.users"></span></h4>
            <div class="form-row node-input-server-users-container-row" style="margin-bottom: 0px;">
                <div id="node-input-server-users-container-div"
                    style="box-sizing: border-box; border-radius: 5px;
                    height: 210px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
                    <ol id="node-input-server-users-container" style="list-style-type:none; margin: 0;"></ol>
                </div>
            </div>
            <div class="form-row">
                <a href="#" class="editor-button editor-button-small" id="node-input-server-users-add"
                style="margin-top: 4px;"><i class="fa fa-plus"></i>
                <span data-i18n="opcua-iiot-contrib.label.addButton"></span></a>
            </div>
            <h4><span data-i18n="opcua-iiot-contrib.label.xmlsets"></span></h4>
            <div class="form-row node-input-server-xmlsets-container-row" style="margin-bottom: 0px;">
                <div id="node-input-server-xmlsets-container-div"
                    style="box-sizing: border-box; border-radius: 5px;
                    height: 240px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
                    <ol id="node-input-server-xmlsets-container" style="list-style-type:none; margin: 0;"></ol>
                </div>
            </div>
            <div class="form-row">
                <a href="#" class="editor-button editor-button-small" id="node-input-server-xmlsets-add"
                style="margin-top: 4px;"><i class="fa fa-plus"></i>
                <span data-i18n="opcua-iiot-contrib.label.addButton"></span></a>
            </div>
        </div>
        <div id="opcuaiiot-server-tab-ass" style="display:none">
            <div class="form-row" style="margin-bottom: 0px;">
                <label for="node-input-addressSpaceScript" style="width:50%;"><i class="fa fa-wrench"></i> <span data-i18n="opcua-iiot-contrib.label.function"></span></label>
                <input type="hidden" id="node-input-addressSpaceScript">
            </div>
            <div class="form-row node-text-editor-row" id="div-script-editor">
                <div style="height: 450px; min-height:250px;" class="node-text-editor" id="node-input-func-editor-addressSpaceScript"></div>
            </div>
        </div>
        <div id="opcuaiiot-server-tab-discovery" style="display:none">
            <div class="form-row">
                <!-- SecurityPolicy enum via REST -->
                <label for="node-input-registerServerMethod" style="min-width:160px"><i class="fa fa-discover"></i>
                <span data-i18n="opcua-iiot-contrib.label.registerServerMethod"></span></label>
                <select type="text" id="node-input-registerServerMethod">
                    <option value="1">HIDDEN</option>
                    <option value="2">MDNS</option>
                    <option value="3">LDS</option>
                </select>
            </div>
            <div class="form-row">
                <span style="min-width:160px">
                  <ul>
                    <li>HIDDEN: the server doesn't expose itself to the external world
                    <li>MDNS: the server publish itself to the mDNS Multicast network directly
                    <li>LDS: the server registers itself to the LDS or LDS-ME (Local Discovery Server)
                  </ul>
                </span>
            </div>
            <div class="form-row">
                 <label for="node-input-serverDiscovery" style="min-width:160px"><i class="fa fa-cc-discover"></i>
                 <span data-i18n="opcua-iiot-contrib.label.serverDiscovery"></span></label>
                <input type="checkbox" id="node-input-serverDiscovery" style="max-width:35px">
            </div>
            <div class="form-row">
                <label for="node-input-discoveryServerEndpointUrl" style="min-width:160px"><i class="icon-discover"></i>
                <span data-i18n="opcua-iiot-contrib.label.discoveryServerEndpointUrl"></span></label>
                <input type="text" id="node-input-discoveryServerEndpointUrl" placeholder="opc.tcp://localhost:4840">
            </div>
            <div class="form-row">
                <label for="node-input-capabilitiesForMDNS" style="min-width:160px"><i class="icon-discover"></i>
                <span data-i18n="opcua-iiot-contrib.label.capabilitiesForMDNS"></span></label>
                <input type="text" id="node-input-capabilitiesForMDNS" placeholder="NA,DA,..." >
            </div>
        </div>
    </div>

</script>

<script type="text/x-red" data-help-name="OPCUA-IIoT-Flex-Server">
    <h2>OPC UA IIoT Flex Server</h2>

    <p>The Flex Server node is an OPC UA server with a flexible address space to build your own information model.
    The address space is to expand with JS based on node-opcua API.
    The Flex Server did not work with ASO requests, but it works also with the Command node.</p>

    <p>Default enpoint: opc.tcp://localhost:55380/</p>
    <p>Named enpoint: opc.tcp://localhost:55380/UA/NodeREDFlexIIoTServer</p>
    <p>Default discovery: opc.tcp://localhost:4840/UAFlexDiscovery</p>
    <p>all discovery: opc.tcp://localhost:4840/</p>

    <h3>Options</h3>
    <p>If you need more information about all options read the node-opcua API, please!</p>

    <p>The 'Delay On Close' is to close the server node with delay to all other nodes.</p>

    <h3>Certificates</h3>
    <p>The server provides simple NodeOPCUA demo certificates and private keys.</p>
    <p>You could set up your own certificate and private key files. This is optional.</p>

    <h3>User</h3>
    <p>The user list is to set up users to the server. It is stored with Node-RED credentials best practice.</p>

    <h3>XML NodeSets</h3>
    <p>That is static for now to add ISA95, DI, and Auto-ID to a server via XML NodeSet.</p>

    <h3>AddressSpace</h3>
    <p>A function like programming editor to construct your own flexible address space in the server.</p>

    <h3>Discovery</h3>
    <p>That gives you access to the servers discovery node-opcua parameters.</p>

    <p>Set showErrors to get errors from node-opcua on browse.</p>
</script>
